import { NodeEventType, Node, Component } from 'cc';

export type Constructor<T> = new (...args: any[]) => T;

export interface PoolHandlerComponent extends Component {
    unuse(): void;
    reuse(...args: any[]): void;
}

export default class NodeGroup {

    public declare poolHandlerComp?: Constructor<PoolHandlerComponent> | string;
    private children: Node[] = [];

    constructor(poolHandlerComp?: Constructor<PoolHandlerComponent> | string) {
        this.poolHandlerComp = poolHandlerComp;
    }

    public size(): number {
        return this.children.length;
    }

    public clear(destroyChild: boolean = false): NodeGroup {
        const children = this.children;
        for (let i = 0; i < children.length; i++) {
            const node = children[i];
            node.off(NodeEventType.NODE_DESTROYED, this.remove, this);
            if (destroyChild) {
                node.destroy();
            }
        }
        this.children.length = 0;
        return this;
    }

    public put(obj: Node): void {
        if (obj && this.children.indexOf(obj) < 0) {
            // Remove from parent, but don't cleanup
            if (obj.parent) {
                obj.removeFromParent();
            }

            // Invoke pool handler
            // @ts-ignore
            const handler = this.poolHandlerComp ? obj.getComponent(this.poolHandlerComp) : null;
            if (handler && handler.unuse) {
                handler.unuse();
            }

            this.children.push(obj);
            obj.once(NodeEventType.NODE_DESTROYED, this.remove, this)
        }
    }

    public get(...args: any[]): Node | null {
        const last = this.children.length - 1;
        if (last < 0) {
            return null;
        } else {
            const obj = this.children[last];
            this.children.length = last;

            // @ts-ignore
            const handler = this.poolHandlerComp ? obj.getComponent(this.poolHandlerComp) : null;
            if (handler && handler.reuse) {
                handler.reuse(...args);
            }
            return obj;
        }
    }

    public remove(obj: Node, destroyChild: boolean = false) {
        const index = this.children.indexOf(obj);
        if (index < 0) {
            return this;
        }
        this.children.splice(index, 1);
        obj.off(NodeEventType.NODE_DESTROYED, this.remove, this);
        if (destroyChild) {
            obj.destroy();
        }
        return this;
    }
}